#include "types.h"
#include "user.h"
#include "param.h"
#include "uthread.h"

uthread_t *cThread = 0;

int nextTid = 0;
int firstThread = 1;
uthread_t * ttable[MAX_UTHREADS];
void wrap_function(void (*entry)());

int
getNumberOfActiveThreads(){
    int counter = 0;
    int i;
    for(i=0;i<MAX_UTHREADS;i++){
	if (ttable[i]==0)
	    break;
	counter++;
    }
    return counter;
}

uthread_t*
sched_rr(void){
    
    int size=getNumberOfActiveThreads();

    uthread_t* next_th = ttable[(cThread->tid)%size]; //getting the next thread id in a cyclic manner	
    return next_th;
}

uthread_t*
sched_pb(void){

    int i,j;
    uthread_t* next_th=0;
    int size=0;
    int cIndex;
    
	// find the size of the threads list and also find the index of current thread in the list
	for(i=0;i<MAX_UTHREADS;i++){
		if (ttable[i]==0)
			break;
		else if (ttable[i]==cThread)
			cIndex=i;
		size++;
    }
    
    if (cThread!=0){
		for(i=0;i<10;i++){
			for(j=1;j<=size;j++){
				// searching the thread list in circle for the next thread.
				// if the current thread is the only thread with a priority we skip this priority
				// in order to prevernt starvation
				if (ttable[(cIndex+j)%size]->priority==i){
					next_th=ttable[(cIndex+j)%size];
					return next_th;
				}
				
			}
		}
    }
    else{
		
		for(i=0;i<10;i++){
			for(j=0;j<size;j++){
				if (ttable[j]->priority==i){
					next_th=ttable[j];
					return next_th;
				}
			}
		}
		
    }
	
	if (size==0){
		next_th=0;
	}
	
    return next_th;
}

int 
uthread_create(void (*start_func)(), int priority){
	
    if (firstThread==1){ // initalize the thread table for the first time.
	memset(ttable,0,sizeof(ttable));
	firstThread=0;
    }
	
    uthread_t* th;
    
    th = malloc(sizeof(uthread_t)); // allocating space for thread - TCB
    th->ss_sp = malloc(UTSTACKSIZE) + UTSTACKSIZE; // allocate stack
    if (th==0 || th->ss_sp==0){ // check for failure
	    return -1;
    }
    
    // Initialize the fields of the TCB
    th->tid = ++nextTid; // setting thread uniqe id
    th->priority = priority; // setting thread prority
    th->ss_size = UTSTACKSIZE;
    th->performYield = 0;
    th->esp = th->ss_sp;
    th->ebp = th->ss_sp;
    
    
    void * tmpMainEsp;
    STORE_ESP(tmpMainEsp);
    LOAD_ESP(th->esp);
    PUSH(start_func);
    STORE_ESP(th->esp);
    LOAD_ESP(tmpMainEsp);

    int i;
    for (i=0; i<MAX_UTHREADS; i++){
	    if (ttable[i]==0){
		break;
	    }
    }
    ttable[i] = th;
    
    return th->tid;
}

void
uthread_yield(void)
{
    uthread_t * tmp = cThread;

    #ifdef UTHREAD_SCHED_RR
	cThread = sched_rr();
    #elif UTHREAD_SCHED_PRIORITY
	cThread = sched_pb();
    #endif

    if (tmp!=0){ // check for null,come from exit situtation,PB of 2 threads,thread A finished and exit.
	STORE_ESP(tmp->esp);
	STORE_EBP(tmp->ebp);
    }
    LOAD_ESP(cThread->esp);
    LOAD_EBP(cThread->ebp);
    if (cThread->performYield==0){
	cThread->performYield=1;
	CALL(wrap_function);   
    }
}

void
releaseResources(uthread_t *th){
    free(th->ss_sp-UTSTACKSIZE);
    free(th);
}

void 
uthread_exit(){

    if (getNumberOfActiveThreads()==1){
	releaseResources(cThread);
	cThread = 0;
	exit(); 
    }
    else{

	int i;
	for(i=0;i<MAX_UTHREADS;i++){
	    if (ttable[i]==cThread){
		break;
	    }
	}
	for(i=i+1;i<getNumberOfActiveThreads();i++){
	    ttable[i-1] = ttable[i];
	}
	uthread_t * prev = cThread;
	ttable[i-1]=0;
	releaseResources(prev);
	#ifdef UTHREAD_SCHED_RR
	
	#elif UTHREAD_SCHED_PRIORITY
	    cThread=0;
	#endif
	uthread_yield();
    }
}

int
uthread_setpr(int priority){
	int lastPriority = cThread->priority;
	cThread->priority = priority;
	return lastPriority;
}

int 
uthread_getpr(){
	return cThread->priority;
}

uthread_t
uthread_self(){
	return *cThread;
}

int
uthread_start_all(){
    
    if (cThread==0){
	#ifdef UTHREAD_SCHED_RR
	    cThread = ttable[0];
	#elif UTHREAD_SCHED_PRIORITY
	    cThread = sched_pb();
	#endif
	cThread->performYield=1;
	LOAD_ESP(cThread->esp);
	LOAD_EBP(cThread->ebp);
	CALL(wrap_function);
    }
    return -1;
}	

// A function that wraps the entry functipn of a thread.
// This is just a siggestion, fell free to modify it as needed.
void wrap_function(void (*entry)())
{
    entry();
    uthread_exit();
}
